#!/usr/bin/perl

# (C) 2025 Web Server LLC

# Tests for image filter module, convert image format.

###############################################################################

use warnings;
use strict;

use Test::More;

BEGIN { use FindBin; chdir($FindBin::Bin); }

use lib 'lib';
use Test::Nginx qw/:DEFAULT http_content/;

###############################################################################

select STDERR; $| = 1;
select STDOUT; $| = 1;

eval { require GD; };
plan(skip_all => 'GD not installed') if $@;

my $t = Test::Nginx->new()->has(qw/http image_filter/)
	->plan(132)->write_file_expand('nginx.conf', <<'EOF');

%%TEST_GLOBALS%%

daemon off;

events {
}

http {
    %%TEST_GLOBALS_HTTP%%

    server {
        listen %%PORT_8080%%;
        server_name localhost;

        location /check_format {
            image_filter resize 5 5;
            alias %%TESTDIR%%/;
        }

        location /convert_jpeg {
            image_filter convert jpeg;
            image_filter resize 10 10;
            alias %%TESTDIR%%/;
        }

        location /convert_gif {
            image_filter convert gif;
            image_filter rotate 90;
            alias %%TESTDIR%%/;
        }

        location /convert_png {
            image_filter convert png;
            alias %%TESTDIR%%/;
        }

        location /convert_webp {
            image_filter convert webp;
            image_filter_sharpen 50;
            alias %%TESTDIR%%/;
        }

        location /convert_avif {
            image_filter convert avif;
            image_filter resize 5 5;
            image_filter_transparency off;
            alias %%TESTDIR%%/;
        }

        location /convert_heic {
            image_filter convert heic;
            alias %%TESTDIR%%/;
        }

        location /convert_var {
            image_filter convert $arg_format;
            alias %%TESTDIR%%/;
        }

        location /convert_var_resize {
            image_filter resize 5 7;
            image_filter convert $arg_format;
            alias %%TESTDIR%%/;
        }

        location /convert_var_rotate {
            image_filter rotate 90;
            image_filter convert $arg_format;
            alias %%TESTDIR%%/;
        }

        location /convert_var_sharpen {
            image_filter convert $arg_format;
            image_filter_sharpen 60;
            alias %%TESTDIR%%/;
        }

        location /convert_var_transparency {
            image_filter convert $arg_format;
            image_filter_transparency off;
            alias %%TESTDIR%%/;
        }
    }
}

EOF

GD::Image->trueColor(1);

my $im = new GD::Image(100, 120);
my $white = $im->colorAllocate(255, 255, 255);
my $black = $im->colorAllocate(125, 200, 100);

$im->transparent($white);
$im->rectangle(0, 0, 99, 99, $black);

$t->write_file('img.jpeg', $im->jpeg);
$t->write_file('img.gif', $im->gif);
$t->write_file('img.png', $im->png);
$t->write_file('img.webp', pack("A4LA8", "RIFF", 0x22, "WEBPVP8 ") .
	pack("N4", 0x16000000, 0x3001009d, 0x012a0100, 0x01000ec0) .
	pack("N2n", 0xfe25a400, 0x03700000, 0x0000));
$t->write_file('img.avif',
	  "\x00\x00\x00\x20\x66\x74\x79\x70\x61\x76\x69\x66\x00\x00\x00\x00"
	. "\x61\x76\x69\x66\x6d\x69\x66\x31\x6d\x69\x61\x66\x4d\x41\x31\x42"
	. "\x00\x00\x00\xf2\x6d\x65\x74\x61\x00\x00\x00\x00\x00\x00\x00\x28"
	. "\x68\x64\x6c\x72\x00\x00\x00\x00\x00\x00\x00\x00\x70\x69\x63\x74"
	. "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x6c\x69\x62\x61"
	. "\x76\x69\x66\x00\x00\x00\x00\x0e\x70\x69\x74\x6d\x00\x00\x00\x00"
	. "\x00\x01\x00\x00\x00\x1e\x69\x6c\x6f\x63\x00\x00\x00\x00\x44\x00"
	. "\x00\x01\x00\x01\x00\x00\x00\x01\x00\x00\x01\x1a\x00\x00\x00\x50"
	. "\x00\x00\x00\x28\x69\x69\x6e\x66\x00\x00\x00\x00\x00\x01\x00\x00"
	. "\x00\x1a\x69\x6e\x66\x65\x02\x00\x00\x00\x00\x01\x00\x00\x61\x76"
	. "\x30\x31\x43\x6f\x6c\x6f\x72\x00\x00\x00\x00\x6a\x69\x70\x72\x70"
	. "\x00\x00\x00\x4b\x69\x70\x63\x6f\x00\x00\x00\x14\x69\x73\x70\x65"
	. "\x00\x00\x00\x00\x00\x00\x00\x0a\x00\x00\x00\x06\x00\x00\x00\x10"
	. "\x70\x69\x78\x69\x00\x00\x00\x00\x03\x08\x08\x08\x00\x00\x00\x0c"
	. "\x61\x76\x31\x43\x81\x00\x0c\x00\x00\x00\x00\x13\x63\x6f\x6c\x72"
	. "\x6e\x63\x6c\x78\x00\x01\x00\x0d\x00\x01\x80\x00\x00\x00\x17\x69"
	. "\x70\x6d\x61\x00\x00\x00\x00\x00\x00\x00\x01\x00\x01\x04\x01\x02"
	. "\x83\x04\x00\x00\x00\x58\x6d\x64\x61\x74\x12\x00\x0a\x08\x18\x0c"
	. "\xa6\xb0\x40\x43\x40\x61\x32\x42\x13\x40\x02\x08\x20\x85\x00\xc9"
	. "\xd0\x69\xb9\x45\x9c\x7a\x42\x42\xae\xdc\x3c\xe4\xfe\x3c\xa3\xb5"
	. "\x50\x93\x1f\x12\x1e\xa0\xac\xb4\xfa\xfc\xe6\xab\x12\x14\xcc\x64"
	. "\xc6\x1b\xe5\x3d\x6f\x96\xae\x1a\x23\xb7\x19\x45\x1c\xe4\xe4\xb3"
	. "\x3e\xa6\x72\xa0\x9b\x36\xb7\x10\xd0\xe8");
$t->write_file('img.heic',
	  "\x00\x00\x00\x1c\x66\x74\x79\x70\x68\x65\x69\x78\x00\x00\x00\x00"
	. "\x6d\x69\x66\x31\x68\x65\x69\x78\x6d\x69\x61\x66\x00\x00\x02\x59"
	. "\x6d\x65\x74\x61\x00\x00\x00\x00\x00\x00\x00\x21\x68\x64\x6c\x72"
	. "\x00\x00\x00\x00\x00\x00\x00\x00\x70\x69\x63\x74\x00\x00\x00\x00"
	. "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0e\x70\x69\x74"
	. "\x6d\x00\x00\x00\x00\x00\x01\x00\x00\x00\x34\x69\x6c\x6f\x63\x00"
	. "\x00\x00\x00\x44\x40\x00\x02\x00\x01\x00\x00\x00\x00\x02\x7d\x00"
	. "\x01\x00\x00\x00\x00\x00\x00\x01\x01\x00\x02\x00\x00\x00\x00\x03"
	. "\x7e\x00\x01\x00\x00\x00\x00\x00\x00\x00\x24\x00\x00\x00\x38\x69"
	. "\x69\x6e\x66\x00\x00\x00\x00\x00\x02\x00\x00\x00\x15\x69\x6e\x66"
	. "\x65\x02\x00\x00\x00\x00\x01\x00\x00\x68\x76\x63\x31\x00\x00\x00"
	. "\x00\x15\x69\x6e\x66\x65\x02\x00\x00\x00\x00\x02\x00\x00\x68\x76"
	. "\x63\x31\x00\x00\x00\x01\x98\x69\x70\x72\x70\x00\x00\x01\x71\x69"
	. "\x70\x63\x6f\x00\x00\x00\x77\x68\x76\x63\x43\x01\x04\x08\x00\x00"
	. "\x00\x00\x00\x00\x00\x00\x00\x1e\xf0\x00\xfc\xff\xf8\xf8\x00\x00"
	. "\x0f\x03\x60\x00\x01\x00\x17\x40\x01\x0c\x01\xff\xff\x04\x08\x00"
	. "\x00\x03\x00\x9e\x38\x00\x00\x03\x00\x00\x1e\xba\x02\x40\x61\x00"
	. "\x01\x00\x2a\x42\x01\x01\x04\x08\x00\x00\x03\x00\x9e\x38\x00\x00"
	. "\x03\x00\x00\x1e\x90\x04\x10\x20\xb2\xdd\x55\xd3\x5c\xdc\x04\x34"
	. "\x18\x10\x00\x00\x03\x00\x10\x00\x00\x03\x00\x10\x80\x62\x00\x01"
	. "\x00\x08\x44\x01\xc1\x73\x18\x30\x18\x90\x00\x00\x00\x14\x69\x73"
	. "\x70\x65\x00\x00\x00\x00\x00\x00\x00\x40\x00\x00\x00\x40\x00\x00"
	. "\x00\x28\x63\x6c\x61\x70\x00\x00\x00\x0a\x00\x00\x00\x01\x00\x00"
	. "\x00\x06\x00\x00\x00\x01\xff\xff\xff\xca\x00\x00\x00\x02\xff\xff"
	. "\xff\xc6\x00\x00\x00\x02\x00\x00\x00\x10\x70\x69\x78\x69\x00\x00"
	. "\x00\x00\x03\x08\x08\x08\x00\x00\x00\x71\x68\x76\x63\x43\x01\x04"
	. "\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1e\xf0\x00\xfc\xfc\xf8"
	. "\xf8\x00\x00\x0f\x03\x60\x00\x01\x00\x17\x40\x01\x0c\x01\xff\xff"
	. "\x04\x08\x00\x00\x03\x00\x9f\xf8\x00\x00\x03\x00\x00\x1e\xba\x02"
	. "\x40\x61\x00\x01\x00\x26\x42\x01\x01\x04\x08\x00\x00\x03\x00\x9f"
	. "\xf8\x00\x00\x03\x00\x00\x1e\xc0\x82\x04\x16\x5b\xaa\xba\x6b\x9b"
	. "\x02\x00\x00\x03\x00\x02\x00\x00\x03\x00\x02\x10\x62\x00\x01\x00"
	. "\x06\x44\x01\xc1\x73\xc1\x89\x00\x00\x00\x0e\x70\x69\x78\x69\x00"
	. "\x00\x00\x00\x01\x08\x00\x00\x00\x27\x61\x75\x78\x43\x00\x00\x00"
	. "\x00\x75\x72\x6e\x3a\x6d\x70\x65\x67\x3a\x68\x65\x76\x63\x3a\x32"
	. "\x30\x31\x35\x3a\x61\x75\x78\x69\x64\x3a\x31\x00\x00\x00\x00\x1f"
	. "\x69\x70\x6d\x61\x00\x00\x00\x00\x00\x00\x00\x02\x00\x01\x04\x81"
	. "\x02\x04\x83\x00\x02\x05\x85\x02\x06\x87\x83\x00\x00\x00\x1a\x69"
	. "\x72\x65\x66\x00\x00\x00\x00\x00\x00\x00\x0e\x61\x75\x78\x6c\x00"
	. "\x02\x00\x01\x00\x01\x00\x00\x01\x2d\x6d\x64\x61\x74\x00\x00\x00"
	. "\xfd\x28\x01\xaf\x04\xf2\x08\x8f\x2d\x0b\x80\x2d\x1c\xe2\xa2\x39"
	. "\xac\x80\xfb\xdf\x81\x5b\x0c\x12\x40\x9f\xc7\x57\x3e\x7a\x34\x79"
	. "\xa4\x6a\xda\x93\x29\x2b\x87\xef\x48\xbd\x6c\x0f\x30\x65\x79\x2e"
	. "\xb0\x6a\x54\xbe\xda\x59\x73\xa9\x35\xd7\xcd\x33\x18\x1e\xd4\x0b"
	. "\xca\xda\xd0\x0c\x82\x1f\xd3\x5b\xe7\x9e\x54\xbf\xd5\xda\xa1\x57"
	. "\x84\x93\xf9\x18\x0c\x8f\x33\x74\x0c\x3a\xc7\xdf\x67\x80\xcb\x13"
	. "\x78\x4f\x5b\x90\x37\xc8\x2a\x84\xb3\xbe\x4f\xd9\x54\x59\x10\x35"
	. "\x1c\x66\x99\x7d\x8f\x3a\x15\x47\xc9\xec\xff\x95\x6d\x7f\x8b\x94"
	. "\xcb\x23\x19\xf5\xaa\xf9\xe6\x76\x68\xea\x20\xe3\x0b\xc4\x40\x75"
	. "\x47\xfe\x4d\xdf\x8b\xda\x2d\xe8\x40\x2f\xe8\xa5\xb6\xd9\x3e\x09"
	. "\x06\x6d\x92\x66\x02\x10\x43\x3c\xf7\x02\x5f\x29\x10\xb2\x17\x4b"
	. "\xb6\xa6\x88\x0a\xf1\x91\x64\xf2\x1f\xfd\x41\x25\x36\xe2\x5d\x04"
	. "\x3e\x05\xec\xc7\xdd\x25\x7f\xd7\xf4\x5e\x79\xad\x47\x02\xb5\xcf"
	. "\x54\xd1\xb7\x2f\xec\xaf\x32\xbe\x1e\x69\x45\xd0\xc5\x3d\xa5\xd5"
	. "\x15\xb0\xcf\xb7\xb5\x72\xd5\xf5\x9f\x03\x36\xa3\xa1\x22\x0e\xa5"
	. "\xb4\xcc\xa9\xa8\x43\xfd\xbb\x4c\x7b\xcc\xa3\x7f\x1a\x1c\x00\x00"
	. "\x00\x20\x28\x01\xae\x09\xe4\x24\xa2\x40\x9f\xe9\x92\xff\xf4\xae"
	. "\x6a\xa1\xf1\x95\x71\x1c\xe1\x73\xf4\x78\xf1\xde\xad\x40\x35\x9d"
	. "\x27\x3a");

$t->run();

my @formats = (qw/jpeg gif png webp avif heic/);

my $avif_supported = format_supported('/check_format/img.avif');

my $heic_supported = !rosa13_broken_x265()
	&& format_supported('/check_format/img.heic');

###############################################################################

foreach my $target_format (@formats) {

	foreach my $source_format (@formats) {

		SKIP: {
			skip "convertation from gif to $target_format is not supported", 2
				if $source_format eq 'gif'
				&& $target_format =~ /^(?:avif|heic|webp)$/;

			skip 'AVIF is not supported', 2
				if !$avif_supported
				&& ($source_format eq 'avif' || $target_format eq 'avif');

			skip 'HEIC is not supported', 2
				if !$heic_supported
				&& ($source_format eq 'heic' || $target_format eq 'heic');

			check_format($target_format, $source_format);
		}
	}

	SKIP: {
		skip 'AVIF is not supported', 10
			if $target_format eq 'avif' && !$avif_supported;

		skip 'HEIC is not supported', 10
			if $target_format eq 'heic' && !$heic_supported;

		check_var_format("/convert_var", $target_format);
		check_var_format("/convert_var_resize", $target_format);
		check_var_format("/convert_var_rotate", $target_format);
		check_var_format("/convert_var_sharpen", $target_format);
		check_var_format("/convert_var_transparency", $target_format);
	}
}

###############################################################################

sub check_format {
	my ($target_format, $source_format) = @_;

	my $url = "/convert_$target_format";
	my $r = http_get("$url/img.$source_format");

	like($r, qr!Content-Type: image/$target_format!,
		"$url: content-type $source_format to $target_format");

	my $format = get_file_format(http_content($r));
	is($format, $target_format,
		"$url: convert $source_format to $target_format");
}

sub check_var_format {
	my ($url, $target_format) = @_;

	my $r = http_get("$url/img.jpeg?format=$target_format");

	like($r, qr!Content-Type: image/$target_format!,
		"$url: content-type jpeg to $target_format");

	my $format = get_file_format(http_content($r));
	is($format, $target_format,
		"$url: convert jpeg to $target_format");
}

sub get_file_format {
	my ($img_data) = @_;

	if ($img_data =~ /^\x89PNG\r\n\x1a\n/) {
		return 'png';

	} elsif ($img_data =~ /^\xFF\xD8\xFF/) {
		return 'jpeg';

	} elsif ($img_data =~ /^GIF8[79]a/) {
		return 'gif';

	} elsif ($img_data =~ /^RIFF....WEBP/) {
		return 'webp';

	} elsif ($img_data =~ /^\x00\x00\x00 ftypavif/) {
		return 'avif';

	} elsif ($img_data =~ /^\x00\x00\x00\x1cftyphei(x|c)/) {
		return 'heic';
	}

	return 'none';
}

sub format_supported {
	my ($file) = @_;
	my $r = http_get($file);
	return !($r =~ qr/415/);
}

sub rosa13_broken_x265 {
	open(my $fh, '<', '/etc/os-release') or return 0;
	my $data = do { local $/; <$fh> };
	close($fh);

	my $id = ($data =~ /^ID=(\w+)/m) ? $1 : '';
	my $version = ($data =~ /^VERSION_ID=(\S+)/m) ? $1 : '';

	if ($id ne 'rosa' || $version ne 13) {
		return 0;
	}

	return (-e '/usr/lib64/libx265.so.212') ? 1 : 0;
}

